package net.xdclass.rbac_shiro.config;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created whit IntelliJ IDEA
 * User:杨骏杰
 * Date:2021/2/16
 * Time:10:55
 * shiro过滤器配置
 * /**
 *          * anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter 没有参数，表示可以匿名使用。
 *          * authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter 表示需要认证(登录)才能使用，没有参数
 *          * authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 没有参数表示httpBasic认证
 *          * logout-------------org.apache.shiro.web.filter.authc.LogoutFilter
 *          * noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter
 *          * perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter 参数可以写多个，多个时必须加上引号，并且参数之间用逗号分割，例如/admins/user/**=perms["user:add:*,user:modify:*"]，当有多个参数时必须每个参数都通过才通过，想当于isPermitedAll()方法。
 *          * port---------------org.apache.shiro.web.filter.authz.PortFilter port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等，serverName是你访问的host,8081是url配置里port的端口，queryString是你访问的url里的？后面的参数。
 *          * rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter 根据请求的方法，相当于/admins/user/**=perms[user:method] ,其中method为post，get，delete等。
 *          * roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 参数可以写多个，多个时必须加上引号，并且参数之间用逗号分割，当有多个参数时，例如admins/user/**=roles["admin,guest"],每个参数通过才算通过，相当于hasAllRoles()方法。
 *          * ssl----------------org.apache.shiro.web.filter.authz.SslFilter 没有参数，表示安全的url请求，协议为https
 *          * user---------------org.apache.shiro.web.filter.authz.UserFilter 没有参数表示必须存在用户，当登入操作时不做检查
 */
@Configuration
public class shiroConfig {

    /**
     * shiro过滤器配置
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
        System.out.println("执行 ShiroFilterFactoryBean.shiroFilter");

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        //必须设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //需要登陆的接口，如果访问某个接口，需要登陆却没有登陆，则调用此接口(如果不是前后端分离则跳转界面)
        shiroFilterFactoryBean.setLoginUrl("/pub/need_login");

        //登陆成功，跳转url，如果前后端分离，则忽略此调用
        shiroFilterFactoryBean.setSuccessUrl("/");

        //登陆成功，未授权就会调用此接口，(先驗證登陆-->再驗證是否有对应的权限)
        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");

        //设置自定义filter
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        //配置自定义filter,并设置自定义filter名称
        filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        //自定义Filter绑定ShiroFilterFactoryBean,覆盖父类方法
        shiroFilterFactoryBean.setFilters(filterMap);

        //坑1：拦截器路径，如果不使用LinkedHashMap，会导致部分路径无法进行拦截，时有时无，因为使用hashmap,无序的，应该使用LinkedHashMap
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        //退出过滤器,DefinitionMap自带退出
        filterChainDefinitionMap.put("/logout","logout");
        //游客访问过滤器
        filterChainDefinitionMap.put("/pub/**","anon");
        //登陆用户才能访问的
        filterChainDefinitionMap.put("/authc/**","authc");

        //管理员才可以访问的,可以配置集合（roles[admin,root]）,但是缺点是
        // 因爲底层是hasAllRoles所以必须满足集合中的全部条件才可以访问
        filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin,root]");
        //有编辑权限才可以访问
        filterChainDefinitionMap.put("/video/update","perms[video_update]");

        //坑2：过滤器是顺序执行，从上到下，一般将/**放在最下面，因为用户权限进来可能会命中一些全局配置路径

        //authc ：url定义必须通过认证才可以访问
        //anon : url可以匿名访问
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }


    /**
     * 设置securityManager对象并注入到shiroFilter中
     * @return
     */
    @Bean
    public SecurityManager securityManager(){
        //此位置需要查看自定义会话管理器继承的是那一种方法
        /*
        DefaultSessionManager ：默认实现，常用于javase
        ServletContainerSessionManager ：web环境
        DefaultWebSessionManager：常用于自定义实现
         */
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //使用自定义的cachemanger缓存
        securityManager.setCacheManager(cacheManager());

        //securityManager绑定sessionManager()对象
        // 如果不是前后端分离，则不必设置下面的setSessionManager，因为前端界面会自动传输sessionId到后端
        securityManager.setSessionManager(sessionManager());

        //securityManager绑定customRealm()
        //设置realm(推荐放下面，不然某些情况会失效)
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    /**
     * 自定义Realm
     * 注入customRealm
     * 执行顺序问题吧customRealm中存储的对象注入到securityManager中，因为可能会在这个bean对象方法中做一些判断，
     * 所以要根据顺序执行 （customRealm() --->securityManager()）
     */
    @Bean
    public CustomRealm customRealm(){
        CustomRealm customRealm = new CustomRealm();
        //设置数据加密器，进行数据加密（hashedCredentialsMatcher()）
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }

    /**
     * HashedCredentialsMatcher 认证匹配
     * HashedCredentialsMatcher 正是 CredentialsMatcher实现类
     * CredentialsMatcher 中Shiro 提供了用于加密密码和验证密码服务
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();

        //设置散列算法：可以使用任意的加密手段：MD5
        credentialsMatcher.setHashAlgorithmName("md5");

        //散列次数，好比md5*2
        credentialsMatcher.setHashIterations(2);

        return credentialsMatcher;
    }

    /**
     * 自定义会话管理器SessionManager
     * 可以做数据持久化
     * @return
     */
    @Bean
    public SessionManager sessionManager(){
        //得到webSessionManager的类对象然后返回
        CustomSessionManager customSessionManager = new CustomSessionManager();

        //超时时间，默认30分钟，会话超时；方法中的单位是毫秒
        //60000毫秒=1分钟 * 30 为30分钟
        customSessionManager.setGlobalSessionTimeout(60000*30);

        //配置session持久化
        customSessionManager.setSessionDAO(redisSessionDAO());
        return customSessionManager;
    }

    /**
     * 配置redisManger
     */
    public RedisManager getRedisManager(){

        RedisManager redisManager = new RedisManager();
        redisManager.setHost("localhost");
        redisManager.setPort(6379);
        return redisManager;
    }

    /**
     * 配置具体cache实现类
     * @return
     */
    public RedisCacheManager cacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(getRedisManager());
        //设置过期时间，单位是秒，设置60*20为20分钟
        redisCacheManager.setExpire(60*20);
        return redisCacheManager;
    }

    /**
     * 自定义session持久化
     * @return
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(getRedisManager());

        //设置自定义SessionId生成器
        redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator());
        return redisSessionDAO;
    }

    /**
     * shiro管理bean的生命周期，以及初始化与销毁
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * api Controller 层面
     * 用于让shiro注解生效
     * AuthorizationAttributeSourceAdvisor
     * 作用：加入注解的使用，不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 用于扫描上下文寻找所有的Advistor(通知器)
     * 作用: 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中，需要在LifecycleBeanPostProcessor创建后才可以创建
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setUsePrefix(true);
        return defaultAdvisorAutoProxyCreator;
    }
}
